<?php
/**
 * @Author: Administrator
 * @Date: 2016/8/17
 * @Last Modified by: Administrator
 * @Last Modified time: 2016/8/17
 */
namespace Framework;

use Closure;
use ReflectionClass;
use ReflectionParameter;

class Container
{
    protected $counter = 1;

    protected function getCounter()
    {
        echo "<br>---------$this->counter----------<br>";
        $this->counter++;
    }

    public $bindings = [];

    public function bind($abstract, $concrete = null, $shared = null)
    {
        $this->getCounter();
        var_dump(__METHOD__,  $abstract, $concrete, $shared);
        if (!$concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }
        $this->bindings[$abstract] = compact('concrete', 'shared');
    }

    protected function getClosure($abstract, $concrete)
    {
        $this->getCounter();
        var_dump(__METHOD__, $abstract, $concrete);
        return function ($c) use ($abstract, $concrete) {
            $method = $abstract == $concrete ? 'build' : 'make';
            return $c->$method($concrete);
        };
    }

    protected function getConcrete($abstract)
    {
        $this->getCounter();
        var_dump(__METHOD__, $abstract);
        if (!isset($this->bindings[$abstract])) {
            return $abstract;
        }
        return $this->bindings[$abstract]['concrete'];
    }

    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    public function make($abstract)
    {
        $this->getCounter();
        var_dump(__METHOD__, $abstract);
        $concrete = $this->getConcrete($abstract);
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }
        return $object;
    }

    public function build($concrete)
    {
        $this->getCounter();
        var_dump(__METHOD__, $concrete);
        if ($concrete instanceof Closure) {
            return $concrete($this);
        }
        $reflector = new ReflectionClass($concrete);
        if (!$reflector->isInstantiable()) {
            echo $message = "Target {$concrete} is not instantiable.";
        }
        $constructor = $reflector->getConstructor();
        if (is_null($constructor)) {
            return new $concrete;
        }
        $dependencies = $constructor->getParameters();
        $instances = $this->getDependencies($dependencies);
        return $reflector->newInstanceArgs($instances);
    }

    protected function getDependencies($paramenters)
    {
        $dependencies = [];
        foreach ($paramenters as $paramenter) {
            $dependency = $paramenter->getClass();
            if (is_null($dependency)) {
                $dependencies[] = null;
            } else {
                $dependencies[] = $this->resolveClass($paramenter);
            }
        }
        return (array) $dependencies;
    }

    protected function resolveClass(ReflectionParameter $parameter)
    {
        return $this->make($parameter->getClass()->name);
    }
}